package com.zz.common.app;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.util.Properties;

import com.zz.common.utils.DateTimeUtil;
import com.zz.common.utils.ZLog;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Process;

public class CrashController implements UncaughtExceptionHandler {
	
	public interface OnCrashListener {
		public boolean onCrash(String filePath, CrashController handler);
	}
	
	private static final String TAG = "CrashController";
	
	
	/** 
	 *  
	 */
	private Thread.UncaughtExceptionHandler mDefaultHandler;
	
	private Context mContext;
	
	/** 
	 */
	private Properties mDeviceCrashInfo = new Properties();
	
	private static final String VERSION_NAME = "versionName";
	private static final String VERSION_CODE = "versionCode";

	private static final String CRASH_REPORTER_EXTENSION = ".txt";
	
	private OnCrashListener mListener;

	private String mCrashDirPath;
	private String mCrashFilePath;
	

	public CrashController(Context ctx, String logFileDirPath, OnCrashListener l) {
		mContext = ctx;
		mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
		mCrashDirPath = logFileDirPath;
		mListener = l;
	}

	@Override
	public void uncaughtException(Thread thread, Throwable ex) {
		if (!handleException(ex) && mDefaultHandler != null) {
			mDefaultHandler.uncaughtException(thread, ex);
		} else {
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
			}			
			Process.killProcess(Process.myPid());
		}
	}

	private boolean handleException(Throwable ex) {
		if (ex == null) {
			return true;
		}
		
		collectCrashDeviceInfo(mContext);
		mCrashFilePath = saveCrashInfoToFile(ex);
		ZLog.d(BaseConfig.TAG, ZLog.LOG_LEVEL_KEY, "Crash File: " + mCrashFilePath);
		
		invokeCrashToListener();
		return true;
	}
	
	private void invokeCrashToListener() {
		if(null != mListener) {
			mListener.onCrash(mCrashFilePath, this);
		}
	}

	private String saveCrashInfoToFile(Throwable ex) {
		Writer info = new StringWriter();
		PrintWriter printWriter = new PrintWriter(info);
		ex.printStackTrace(printWriter);
		Throwable cause = ex.getCause();
		while (cause != null) {
			cause.printStackTrace(printWriter);
			cause = cause.getCause();
		}
		String result = "\nstack:\n"+info.toString();
		ZLog.d(TAG, result);
		
		printWriter.close();
		
		FileOutputStream trace = null;
		String fileName = null;
		try {
			long timestamp = System.currentTimeMillis();		
			
			String path = mCrashDirPath;
			File pathFile = new File(path);
			if(!pathFile.exists() || !pathFile.isDirectory()) {
				pathFile.mkdirs();
			}
			fileName = path+"crash-" + DateTimeUtil.getDateString(timestamp) + CRASH_REPORTER_EXTENSION;

			trace = new FileOutputStream(new File(fileName));
			mDeviceCrashInfo.store(trace, "");
			trace.write(result.getBytes());
			trace.flush();		
			
		} catch (Exception e) {
			ZLog.d(TAG, "an error occured while writing report file..."+ e.toString());
			fileName = null;
		} finally {
			if(null != trace) {
				try {
					trace.close();
				} catch(Exception e) {
					e = null;
				}
			}
		}
		return fileName;
	}

	private void collectCrashDeviceInfo(Context ctx) {
		try {
			PackageManager pm = ctx.getPackageManager();
			PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(),
					PackageManager.GET_ACTIVITIES);
			if (pi != null) {
				mDeviceCrashInfo.put(VERSION_NAME,
						pi.versionName == null ? "not set" : pi.versionName);
				mDeviceCrashInfo.put(VERSION_CODE, pi.versionCode+"");
			}
		} catch (NameNotFoundException e) {
			ZLog.d(TAG, "Error while collect package info"+ e.toString());
		}

		Field[] fields = Build.class.getDeclaredFields();
		for (Field field : fields) {
			try {
				field.setAccessible(true);
				mDeviceCrashInfo.put(field.getName(), field.get(null)+"");
				ZLog.d(TAG, field.getName() + " : " + field.get(null));
			} catch (Exception e) {
				ZLog.d(TAG, "Error while collect crash info"+ e);
			}
		}
	}
}
